﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Threading;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    using Phuse.Net.Nntp;

    /// <summary>Tests the <see cref="Cache"/> class with a local fake server.</summary>
    [TestClass]
    public sealed class LocalTest : TestInfrastructure
    {
        /// <summary>Tests the case where a message has an In-Reply-To field but no References field. Moreover, the
        /// case is tested when an article is removed between header and body receive.</summary>
        [TestMethod]
        public void InReplyToTest()
        {
            // TODO: We're no longer checking the In-Reply-To field, it seems at least part of this test is superfluous.
            TestWithFakeServer(
                (server, cache) =>
                {
                    var messageId1 = GetUniqueString();
                    var subject1 = GetUniqueString();
                    var messageId2 = GetUniqueString();

                    // Use overlong subject for second article
                    StringBuilder subject2Builder = new StringBuilder(256);

                    while (subject2Builder.Length <= 128)
                    {
                        subject2Builder.Append(GetUniqueString());
                    }

                    var subject2 = subject2Builder.ToString();

                    QueueSucceedingGroupCommand(server);
                    QueueOverCommand(server, 1, 1, messageId1, subject1, null, 2, messageId2, subject2, messageId1);
                    var observer = new ConsoleProgressObserver();
                    WaitForSendReceiveToFinish(cache, Receive(cache, false, observer), observer);
                    observer.AssertNoExceptions();

                    var group = cache.Hosts.Single().Groups.Single();
                    Assert.IsTrue(group.GetOriginalInboundArticles(group.InboundArticleFilter).Count() == 1);
                    var article1 = group.GetOriginalInboundArticles(group.InboundArticleFilter).First();
                    Assert.AreEqual(subject1, article1.Subject);
                    Assert.IsTrue(article1.GetFollowupInboundArticles().Count() == 1);
                    Assert.IsTrue(subject2.StartsWith(
                        article1.GetFollowupInboundArticles().First().Subject, StringComparison.Ordinal));

                    // Our FakeServer can only accept exactly one connection, we need to create a new one if we want
                    // to connect again
                    server.Dispose();

                    using (server = NntpFakeServerHelper.MakeAvailableServer(true))
                    {
                        // Test article removed between overview & article receive
                        article1.IsBeingWatched = true;
                        cache.SaveChanges();

                        QueueSucceedingGroupCommand(server);
                        server.QueueCommand("ARTICLE 1\r\n", "423 No article with that number\r\n");
                        WaitForSendReceiveToFinish(
                            cache,
                            cache.SendReceiveAsync(new[] { group }, false, CancellationToken.None, null),
                            observer);
                        observer.AssertNoExceptions();
                        group = cache.Hosts.Single().Groups.Single();
                        Assert.IsTrue(group.GetOriginalInboundArticles(group.InboundArticleFilter).Count() == 1);
                        var article2 = group.GetOriginalInboundArticles(group.InboundArticleFilter).First();
                        Assert.IsTrue(subject2.StartsWith(article2.Subject, StringComparison.Ordinal));
                        Assert.IsTrue(article2.GetFollowupInboundArticles().Count() == 0);
                    }
                });
        }

        /// <summary>Tests what happens when the first message has been removed.</summary>
        [TestMethod]
        public void MissingFirstArticleTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var messageId1 = GetUniqueString();
                    var subject1 = GetUniqueString();
                    QueueSucceedingGroupCommand(server);
                    QueueOverCommand(server, 1, 2, messageId1, subject1, null);
                    var observer = new ConsoleProgressObserver();
                    WaitForSendReceiveToFinish(cache, Receive(cache, false, observer), observer);
                    var group = cache.Hosts.Single().Groups.Single();
                    observer.AssertNoExceptions();

                    Assert.IsTrue(group.GetOriginalInboundArticles(group.InboundArticleFilter).Count() == 1);
                    var article1 = group.GetOriginalInboundArticles(group.InboundArticleFilter).First();
                    Assert.AreEqual(subject1, article1.Subject);
                    Assert.IsTrue(article1.GetFollowupInboundArticles().Count() == 0);
                });
        }

        /// <summary>Tests what happens when a subsequent message has been removed.</summary>
        [TestMethod]
        public void MissingSubsequentArticleTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var subject = GetUniqueString();
                    var article1 = ReceiveSingleArticle(server, cache, GetUniqueString(), subject);
                    var group = article1.Group;

                    Assert.IsTrue(group.GetOriginalInboundArticles(group.InboundArticleFilter).Count() == 1);
                    Assert.AreEqual(subject, article1.Subject);
                    Assert.IsTrue(article1.GetFollowupInboundArticles().Count() == 0);
                });
        }

        /// <summary>Tests an empty group corner case.</summary>
        [TestMethod]
        public void EmptyGroupTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    server.QueueCommand("GROUP " + OurGroupName + "\r\n", "211 0 1 2 our.group\r\n");
                    var observer = new ConsoleProgressObserver();
                    WaitForSendReceiveToFinish(cache, Receive(cache, false, observer), observer);
                    observer.AssertNoExceptions();
                });
        }

        /// <summary>Tests the case when a message-ID is reused (illegal). Moreover, the case is tested when the server
        /// responds with a different article number than was requested.</summary>
        [TestMethod]
        public void ReusedMessageIdTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var messageId = GetUniqueString();
                    var subject1 = GetUniqueString();
                    var subject2 = GetUniqueString();

                    QueueSucceedingGroupCommand(server);
                    QueueOverCommand(server, 1, 1, messageId, subject1, null, 2, messageId, subject2, null);
                    QueueArticleCommand(server, 1, 2, messageId, subject2, null);
                    var observer = new ConsoleProgressObserver();
                    WaitForSendReceiveToFinish(cache, Receive(cache, true, observer), observer);
                    observer.AssertCallCount(new UnexpectedResponseException(), 1);
                    var group = cache.Hosts.Single().Groups.Single();

                    Assert.IsTrue(group.GetOriginalInboundArticles(group.InboundArticleFilter).Count() == 1);
                    var article1 = group.GetOriginalInboundArticles(group.InboundArticleFilter).First();
                    Assert.AreEqual(subject1, article1.Subject);
                    Assert.IsTrue(article1.GetFollowupInboundArticles().Count() == 0);
                });
        }

        /// <summary>Tests what happens when a group is deleted before the cache had the chance to receive articles
        /// for it.</summary>
        [TestMethod]
        public void NonexistentGroupTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var observer = new ConsoleProgressObserver();
                    var group = cache.AddHost<NntpForumProvider, NntpConnection>(
                        "localhost", 119).AddGroup(OurGroupName, "Our Group", 100, 100);
                    Assert.IsFalse(cache.SaveChanges());
                    group.Delete();
                    Assert.IsTrue(cache.SaveChanges());
                    var task = cache.SendReceiveAsync(new[] { group }, false, CancellationToken.None, null);

                    try
                    {
                        WaitForSendReceiveToFinish(cache, task, observer);
                        Assert.Fail("Unexpected success!");
                    }
                    catch (TargetInvocationException ex)
                    {
                        Assert.IsInstanceOfType(ex.InnerException, typeof(ArgumentException));
                    }
                });
        }

        /// <summary>Tests deleted host exceptional use cases.</summary>
        [TestMethod]
        public void DeletedHostTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var inboundArticle = ReceiveSingleArticle(server, cache, GetUniqueString(), GetUniqueString());
                    var group = inboundArticle.Group;
                    var host = group.Host;
                    var outboundArticle = cache.GetOutboundArticles(SendStatus.Draft).Single();
                    host.Delete();
                    Assert.IsTrue(cache.SaveChanges());
                    CheckInvalidHost(host, outboundArticle, group, inboundArticle, true);
                });
        }

        /// <summary>Tests deleted group exceptional use cases.</summary>
        [TestMethod]
        public void DeletedGroupTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var article = ReceiveSingleArticle(server, cache, GetUniqueString(), GetUniqueString());
                    var group = article.Group;
                    group.Delete();
                    Assert.IsTrue(cache.SaveChanges());
                    CheckInvalidGroup(group, article, true);
                });
        }

        /// <summary>Tests what happens when the <see cref="Cache"/> object is disposed prematurely.</summary>
        [TestMethod]
        public void CacheDisposedTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var inboundArticle = ReceiveSingleArticle(server, cache, GetUniqueString(), GetUniqueString());
                    var group = inboundArticle.Group;
                    var outboundArticle = cache.GetOutboundArticles(SendStatus.Draft).Single();
                    cache.Dispose();

                    CheckInvalidCache(cache, group);
                    CheckInvalidHost(group.Host, outboundArticle, group, inboundArticle, false);
                });
        }

        /// <summary>Tests what happens when a server responds that the service is unavailable.</summary>
        [TestMethod]
        public void ServiceUnavailableTest()
        {
            TestWithUnavailableFakeServer(400, ServiceUnavailableTest);
            TestWithUnavailableFakeServer(502, ServiceUnavailableTest);
        }

        /// <summary>Tests what happens when a server responds that there is an internal server fault.</summary>
        [TestMethod]
        public void ServerFaultTest()
        {
            var observer = new ConsoleProgressObserver();
            TestFailingReceive(observer, 403, new ServerFaultException());
        }

        /// <summary>Tests what happens when a server responds that a group is unknown.</summary>
        [TestMethod]
        public void GroupUnknownTest()
        {
            var observer = new ConsoleProgressObserver();
            TestFailingReceive(observer, 411, new NoSuchGroupException());
        }

        /// <summary>Tests what happens when a server responds that the client must authenticate itself.</summary>
        [TestMethod]
        public void AuthenticationMissingTest()
        {
            var observer = new ConsoleProgressObserver();
            TestFailingReceive(observer, 480, new MissingAuthenticationException());
        }

        /// <summary>Tests what happens when a server responds that suitable privacy must be established.</summary>
        [TestMethod]
        public void PrivacyMissingTest()
        {
            var observer = new ConsoleProgressObserver();
            TestFailingReceive(observer, 483, new MissingPrivacyException());
        }

        /// <summary>Tests what happens when a server responds with an unexpected response.</summary>
        [TestMethod]
        public void ResponseUnexpectedTest()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var observer = new ConsoleProgressObserver();
                    QueueFailingGroupCommand(server, 999);
                    WaitForSendReceiveToFinish(cache, Receive(cache, true, observer), observer);
                    observer.AssertEventCount(typeof(ConnectProgress), 2);
                    observer.AssertCallCount(new UnexpectedResponseException(), 1);
                    observer.AssertTotalEventCount(4);
                });
        }

        /// <summary>Tests what happens when a server does not respond after a connection has been established.
        /// </summary>
        [TestMethod]
        public void ConnectionLost1Test()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var observer = new ConsoleProgressObserver();
                    WaitForSendReceiveToFinish(cache, Receive(cache, true, observer), observer);
                    observer.AssertEventCount(typeof(ConnectProgress), 2);
                    observer.AssertCallCount(new ConnectionLostException(), 1);
                    observer.AssertTotalEventCount(4);
                });
        }

        /// <summary>Tests what happens when the connection is lost during the overview download.</summary>
        [TestMethod]
        public void ConnectionLost2Test()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var messageId1 = GetUniqueString();
                    var subject1 = GetUniqueString();
                    QueueSucceedingGroupCommand(server);
                    QueueOverCommand(server, 1, 1, messageId1, subject1, null, partial: true);
                    var observer = new ConsoleProgressObserver();
                    WaitForSendReceiveToFinish(cache, Receive(cache, true, observer), observer);
                    observer.AssertEventCount(typeof(ConnectProgress), 2);
                    observer.AssertCallCount(new ConnectionLostException(), 1);
                    observer.AssertTotalEventCount(5);
                });
        }

        /// <summary>Tests what happens when the connection is lost during the download of an article.</summary>
        [TestMethod]
        public void ConnectionLost3Test()
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    var messageId1 = GetUniqueString();
                    var subject1 = GetUniqueString();
                    QueueSucceedingGroupCommand(server);
                    QueueOverCommand(server, 1, 1, messageId1, subject1, null);
                    QueueArticleCommand(server, 1, 1, messageId1, subject1, null, true);
                    var observer = new ConsoleProgressObserver();
                    WaitForSendReceiveToFinish(cache, Receive(cache, true, observer), observer);
                    observer.AssertEventCount(typeof(ConnectProgress), 2);
                    observer.AssertEventCount(typeof(HeaderReceiveProgress), 2);
                    observer.AssertEventCount(typeof(ContentReceiveProgress), 1);
                    observer.AssertCallCount(new ConnectionLostException(), 1);
                    observer.AssertTotalEventCount(7);
                });
        }

        /// <summary>Tests what happens when the writing of received data fails.</summary>
        [SuppressMessage("Microsoft.Security", "CA2140:TransparentMethodsMustNotReferenceCriticalCodeFxCopRule", Justification = "Test code.")]
        [TestMethod]
        public void WriteFailedTest()
        {
            using (var cache = CreateNewCache())
            {
                var messageId = GetUniqueString();
                var subject = GetUniqueString();
                string groupDirectory = Path.Combine(Path.Combine(cache.DataDirectory, "In"), "1");
                Directory.CreateDirectory(groupDirectory);

                using (var server = NntpFakeServerHelper.MakeAvailableServer(true))
                {
                    QueueSucceedingGroupCommand(server);
                    QueueOverCommand(server, 1, 1, messageId, subject, null);
                    QueueArticleCommand(server, 1, 1, messageId, "Hello", null);
                    var observer = new ConsoleProgressObserver();

                    using (var file = new FileStream(
                        Path.Combine(groupDirectory, "0.dat"), FileMode.Append, FileAccess.Write, FileShare.None))
                    {
                        WaitForSendReceiveToFinish(cache, Receive(cache, true, observer), observer);
                    }

                    observer.AssertEventCount(typeof(ConnectProgress), 2);
                    observer.AssertEventCount(typeof(HeaderReceiveProgress), 2);
                    observer.AssertEventCount(typeof(ContentReceiveProgress), 1);
                    observer.AssertCallCount(new WriteFailedException(), 1);
                    observer.AssertTotalEventCount(7);
                }
            }
        }

        /// <summary>Tests send/receive performance.</summary>
        [TestMethod]
        public void PerformanceTest()
        {
            using (var testServer = new NntpTestServer(119))
            using (Cache cache = CreateNewCache())
            {
                var group = cache.AddHost<NntpForumProvider, NntpConnection>(
                    "localhost", 119).AddGroup(OurGroupName, "Our Group", 5000, 2500);
                cache.SaveChanges();
                var task = cache.SendReceiveAsync(
                    new[] { group }, true, CancellationToken.None, new ConsoleProgressObserver());
                task.Wait();
                group.MarkAllInboundArticlesAsRead();
                cache.SaveChanges();
                cache.Compact();
            }
        }

        /// <summary>Tests the <see cref="IEquatable{T}.Equals"/> implementation of the <see cref="Host"/> class.
        /// </summary>
        [TestMethod]
        public void IHostInfoEqualsTest()
        {
            using (var cache = CreateNewCache())
            {
                var hostInfo = (IHostInfo)cache.AddHost<NntpForumProvider, NntpConnection>("whatever", 119);
                cache.SaveChanges();
                Assert.IsFalse(hostInfo.Equals(null));

                using (var cache2 = new Cache(cache.DataDirectory, new NullCryptoProvider()))
                {
                    Assert.IsTrue(hostInfo.Equals((IHostInfo)cache2.Hosts.First()));
                }
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static void QueueSucceedingGroupCommand(FakeServer server)
        {
            server.QueueCommand("GROUP " + OurGroupName + "\r\n", "211 2 1 2 our.group\r\n");
        }

        private static void QueueFailingGroupCommand(FakeServer server, int responseCode)
        {
            server.QueueCommand(
                "GROUP our.group\r\n", responseCode.ToString(CultureInfo.InvariantCulture) + " bla bla\r\n");
        }

        private static void QueueOverCommand(
            FakeServer server,
            int number,
            int number1,
            string messageId1,
            string subject1,
            string inReplyToMessageId1,
            int number2 = 0,
            string messageId2 = null,
            string subject2 = null,
            string inReplyToMessageId2 = null,
            bool partial = false)
        {
            using (var writer = new StringWriter())
            {
                writer.WriteLine("224 Overview information follows");

                // This is deliberately less than the SQL Server minimal date
                const string Format = "{0}\t{1}\t\t31 Dec 1752 16:59:59 +0000\t{2}\t{3}\t0\t0";
                writer.WriteLine(Format, number1, subject1, messageId1, inReplyToMessageId1);

                if (number2 != 0)
                {
                    writer.WriteLine(Format, number2, subject2, messageId2, inReplyToMessageId2);
                }

                writer.WriteLine(".");

                var response = writer.ToString();
                response = partial ? response.Substring(0, response.Length / 2) : response;
                server.QueueCommand("XOVER " + number + "-\r\n", response);
            }
        }

        private static void QueueArticleCommand(
            FakeServer server,
            int commandNumber,
            int responseNumber,
            string messageId,
            string subject,
            string inReplyToMessageId,
            bool partial = false)
        {
            var response =
                "220 " + responseNumber + " " + messageId + "\r\n" +
                "Subject: " + subject + "\r\n" +
                "Date: 31 Dec 1752 16:59:59 +0000\r\n" + // This is deliberately less than the SQL Server minimal date
                (string.IsNullOrEmpty(inReplyToMessageId) ? null : ("References: " + inReplyToMessageId + "\r\n")) +
                "\r\nwhatever\r\n.\r\n";

            if (partial)
            {
                response = response.Substring(0, response.Length / 2);
            }

            server.QueueCommand("ARTICLE " + commandNumber + "\r\n", response);
        }

        private static IInboundArticle ReceiveSingleArticle(FakeServer server, Cache cache, string messageId, string subject)
        {
            QueueSucceedingGroupCommand(server);
            QueueOverCommand(server, 1, 1, messageId, subject, null);
            var observer = new ConsoleProgressObserver();
            WaitForSendReceiveToFinish(cache, Receive(cache, false, observer), observer);
            var host = cache.Hosts.Single();
            host.AddOutboundArticle();
            cache.SaveChanges();
            return host.Groups.Single().GetOriginalInboundArticles(InboundArticleFilter.NullFilter).Single();
        }

        private static void TestWithFakeServer(Action<FakeServer, Cache> testCallback)
        {
            using (var server = NntpFakeServerHelper.MakeAvailableServer(true))
            using (var cache = CreateNewCache())
            {
                testCallback(server, cache);
            }
        }

        private static void TestWithUnavailableFakeServer(int responseCode, Action<FakeServer, Cache> testCallback)
        {
            using (var server = NntpFakeServerHelper.MakeUnavailableServer(responseCode))
            using (var cache = CreateNewCache())
            {
                testCallback(server, cache);
            }
        }

        private static void ServiceUnavailableTest(FakeServer server, Cache cache)
        {
            var observer = new ConsoleProgressObserver();
            WaitForSendReceiveToFinish(cache, Receive(cache, true, observer), observer);
            observer.AssertEventCount(typeof(ConnectProgress), 1);
            observer.AssertCallCount(new ServiceUnavailableException(), 1);
            observer.AssertTotalEventCount(3);
        }

        private static void TestFailingReceive(
            ConsoleProgressObserver observer, int responseCode, Exception expectedException)
        {
            TestWithFakeServer(
                (server, cache) =>
                {
                    QueueFailingGroupCommand(server, responseCode);
                    WaitForSendReceiveToFinish(cache, Receive(cache, true, observer), observer);
                    observer.AssertEventCount(typeof(ConnectProgress), 2);
                    observer.AssertCallCount(expectedException, 1);
                    observer.AssertTotalEventCount(4);
                });
        }

        [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.Int32.ToString", Justification = "Test code.")]
        private static void CheckInvalidCache(Cache cache, IGroup group)
        {
            Assert.IsNotNull(cache.DataDirectory);
            cache.Dispose();

            AssertThrow<ObjectDisposedException>(
                () => cache.Reset(),
                () => cache.HostNameMaxLength.ToString(),
                () => cache.UserNameMaxLength.ToString(),
                () => cache.EmailAddressMaxLength.ToString(),
                () => cache.AccountNameMaxLength.ToString(),
                () => cache.PasswordMaxLength.ToString(),
                () => cache.GroupNameMaxLength.ToString(),
                () => cache.GroupDisplayNameMaxLength.ToString(),
                () => cache.ArticleSubjectMaxLength.ToString(),
                () => cache.AddHost<NntpForumProvider, NntpConnection>("whatever.com", 119),
                () => cache.Hosts.ToString(),
                () => cache.GetOutboundArticles(SendStatus.Draft),
                () => cache.GetDeletedOutboundArticles(),
                () => cache.GetOutboundArticleCounts(),
                () => cache.SendReceiveAsync(new[] { group }, true, CancellationToken.None, new ConsoleProgressObserver()).Dispose(),
                () => cache.ShouldCompact.ToString(),
                () => cache.Compact(),
                () => cache.SaveChanges());
        }

        private static void CheckInvalidHost(
            IHost host, IOutboundArticle outboundArticle, IGroup group, IInboundArticle inboundArticle, bool deleted)
        {
            Assert.IsNotNull(host.Name);
            Assert.IsNotNull(host.Port);
            Assert.IsNull(host.UserName);
            Assert.IsNull(host.EmailAddress);

            Assert.IsNotNull(outboundArticle.Host);
            Assert.IsNull(outboundArticle.DateUtc);
            Assert.IsNull(outboundArticle.Subject);
            Assert.IsNotNull(outboundArticle.SendStatus);
            Assert.IsNotNull(outboundArticle.IsDeleted);

            AssertThrow<InvalidOperationException>(
                () => host.UserName = string.Empty,
                () => host.EmailAddress = string.Empty,
                () => host.AccountName.ToString(),
                () => host.AccountName = string.Empty,
                () => host.Password.ToString(),
                () => host.Password = string.Empty,
                () => host.AddGroup("my.group", "My", 42, 42),
                () => host.Groups.ToString(),
                () => host.AddOutboundArticle(),
                () => host.Delete(),
                () => outboundArticle.SendStatus = SendStatus.Outbox,
                () => outboundArticle.IsDeleted = true,
                () => outboundArticle.LoadMessage(),
                () => outboundArticle.SaveMessage(new Mime.Message()));

            CheckInvalidGroup(group, inboundArticle, deleted);
        }

        private static void CheckInvalidGroup(IGroup group, IInboundArticle inboundArticle, bool deleted)
        {
            if (deleted)
            {
                AssertThrow<InvalidOperationException>(() => group.Host.ToString());
            }
            else
            {
                Assert.IsNotNull(group.Host);
            }

            Assert.IsNotNull(group.Name);
            Assert.IsNotNull(group.DisplayName);
            Assert.IsNotNull(group.NewInboundArticlesReceiveLimit);
            Assert.IsNotNull(group.CachedInboundArticlesLimit);
            Assert.IsNotNull(group.InboundArticleFilter);

            Assert.IsNotNull(inboundArticle.Group);
            Assert.IsNotNull(inboundArticle.Number);
            Assert.IsNotNull(inboundArticle.DateUtc);
            Assert.IsNull(inboundArticle.From);
            Assert.IsNotNull(inboundArticle.Subject);
            Assert.IsNotNull(inboundArticle.HasFullArticleBeenReceived);
            Assert.IsNull(inboundArticle.HasAttachments);
            Assert.IsNotNull(inboundArticle.IsBeingWatched);
            Assert.IsNotNull(inboundArticle.IsExpanded);
            Assert.IsNotNull(inboundArticle.HasBeenRead);
            Assert.IsNotNull(inboundArticle.IsSelected);

            AssertThrow<InvalidOperationException>(
                () => group.DisplayName = string.Empty,
                () => group.NewInboundArticlesReceiveLimit = 1,
                () => group.InboundArticleFilter = InboundArticleFilter.NullFilter,
                () => group.GetOriginalInboundArticles(InboundArticleFilter.NullFilter),
                () => group.GetOriginalInboundArticleCount(InboundArticleFilter.NullFilter),
                () => group.MarkAllInboundArticlesAsRead(),
                () => group.Delete(),
                () => inboundArticle.HasAttachments = false,
                () => inboundArticle.IsBeingWatched = true,
                () => inboundArticle.IsExpanded = true,
                () => inboundArticle.HasBeenRead = true,
                () => inboundArticle.IsSelected = true,
                () => inboundArticle.GetContent(),
                () => inboundArticle.GetFollowupInboundArticles());
        }
    }
}
